package io.gitee.zhangbinhub.acp.cloud.loadbalancer

import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled
import com.alibaba.cloud.nacos.NacosDiscoveryProperties
import com.alibaba.cloud.nacos.loadbalancer.LoadBalancerAlgorithm
import com.alibaba.cloud.nacos.loadbalancer.ServiceInstanceFilter
import com.alibaba.cloud.nacos.util.InetIPv6Utils
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled
import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled
import org.springframework.cloud.client.ServiceInstance
import org.springframework.cloud.client.discovery.DiscoveryClient
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.core.env.Environment
import java.util.function.Consumer

@AutoConfiguration
@ConditionalOnAcpLoadBalancer
@ConditionalOnNacosDiscoveryEnabled
class AcpCloudNacosLoadBalancerClientConfiguration {
    @Bean
    @ConditionalOnMissingBean
    fun acpNacosLoadBalancer(
        logAdapter: LogAdapter,
        environment: Environment,
        loadBalancerClientFactory: LoadBalancerClientFactory,
        nacosDiscoveryProperties: NacosDiscoveryProperties,
        inetIPv6Utils: InetIPv6Utils,
        serviceInstanceFilters: MutableList<ServiceInstanceFilter>,
        loadBalancerAlgorithms: MutableList<LoadBalancerAlgorithm>
    ): ReactorLoadBalancer<ServiceInstance> {
        val name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME)
        val loadBalancerAlgorithmMap: MutableMap<String, LoadBalancerAlgorithm> = mutableMapOf()
        loadBalancerAlgorithms.forEach(Consumer { loadBalancerAlgorithm ->
            if (!loadBalancerAlgorithmMap.containsKey(loadBalancerAlgorithm.serviceId)) {
                loadBalancerAlgorithmMap[loadBalancerAlgorithm.serviceId] = loadBalancerAlgorithm
            }
        })
        return AcpNacosLoadBalancer(
            logAdapter,
            loadBalancerClientFactory.getLazyProvider(
                name, ServiceInstanceListSupplier::class.java
            ), name, nacosDiscoveryProperties, inetIPv6Utils, serviceInstanceFilters, loadBalancerAlgorithmMap
        )
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnReactiveDiscoveryEnabled
    @Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER)
    class ReactiveSupportConfiguration {
        @Bean
        @ConditionalOnBean(ReactiveDiscoveryClient::class)
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            value = ["spring.cloud.loadbalancer.configurations"],
            havingValue = "default",
            matchIfMissing = true
        )
        fun discoveryClientServiceInstanceListSupplier(
            context: ConfigurableApplicationContext?
        ): ServiceInstanceListSupplier? {
            return ServiceInstanceListSupplier.builder().withDiscoveryClient()
                .build(context)
        }

        @Bean
        @ConditionalOnBean(ReactiveDiscoveryClient::class)
        @ConditionalOnMissingBean
        @ConditionalOnProperty(value = ["spring.cloud.loadbalancer.configurations"], havingValue = "zone-preference")
        fun zonePreferenceDiscoveryClientServiceInstanceListSupplier(
            context: ConfigurableApplicationContext?
        ): ServiceInstanceListSupplier? {
            return ServiceInstanceListSupplier.builder().withDiscoveryClient()
                .withZonePreference().build(context)
        }
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnBlockingDiscoveryEnabled
    @Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER + 1)
    class BlockingSupportConfiguration {
        @Bean
        @ConditionalOnBean(DiscoveryClient::class)
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            value = ["spring.cloud.loadbalancer.configurations"],
            havingValue = "default",
            matchIfMissing = true
        )
        fun discoveryClientServiceInstanceListSupplier(
            context: ConfigurableApplicationContext?
        ): ServiceInstanceListSupplier? {
            return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient()
                .build(context)
        }

        @Bean
        @ConditionalOnBean(DiscoveryClient::class)
        @ConditionalOnMissingBean
        @ConditionalOnProperty(value = ["spring.cloud.loadbalancer.configurations"], havingValue = "zone-preference")
        fun zonePreferenceDiscoveryClientServiceInstanceListSupplier(
            context: ConfigurableApplicationContext?
        ): ServiceInstanceListSupplier? {
            return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient()
                .withZonePreference().build(context)
        }
    }

    companion object {
        const val REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER: Int = 183827465
    }
}